﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using log4net;

namespace MyLib.Data {
	/// <summary>
	/// 
	/// </summary>
	public class CodeFileProvider : FileContextProvider {
		/// <summary>
		/// 
		/// </summary>
		private static ILog m_logger = LogManager.GetLogger(typeof(CodeFileProvider));

		/// <summary>
		/// 
		/// </summary>
		private const string XsdFileName = @"Code.xsd";

		/*
		 * Constants of XML.
		 * 
		 */

		/// <summary>
		/// 
		/// </summary>
		private const string XmlCategoriesName = "categories";

		/// <summary>
		/// 
		/// </summary>
		private const string XmlCategoryName = "category";

		/// <summary>
		/// 
		/// </summary>
		private const string XmlCategoryIdName = "id";

		/// <summary>
		/// 
		/// </summary>
		private const string XmlCodeName = "code";

		/// <summary>
		/// 
		/// </summary>
		private const string XmlCodeTextName = "text";

		/// <summary>
		/// 
		/// </summary>
		private const string XmlCodeValueName = "value";

		/// <summary>
		/// 
		/// </summary>
		private const string XmlCodeParentName = "parent";

		/*
		 * Private members.
		 */

		/// <summary>
		/// 
		/// </summary>
		private string m_codeFileName;

		/// <summary>
		/// 
		/// </summary>
		private string m_cacheKey;

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public CodeTable LoadDataTable() {
			CodeTable dataTable = null;

			if (CacheProvider != null) {
				dataTable = CacheProvider[m_cacheKey] as CodeTable;
			}

			if (dataTable == null) {
				dataTable = LoadDataTableFromFile(m_codeFileName);

				if (CacheProvider != null) {
					CacheProvider.Add(m_cacheKey, dataTable, m_codeFileName);
				}
			}

			return dataTable;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="dataTable"></param>
		public void SaveDataTable(CodeTable dataTable) {
			System.Diagnostics.Debug.Assert(dataTable != null);

			SaveDataTableToFile(m_codeFileName, dataTable);

			if (CacheProvider != null) {
				CacheProvider.Add(m_cacheKey, dataTable, m_codeFileName);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="categoryId"></param>
		/// <param name="value"></param>
		/// <returns></returns>
		public string GetTextFromValue(int categoryId, object value) {
			var r = LoadDataTable().FirstOrDefault(
				row => row.CategoryId == categoryId && row.Value == Convert.ToString(value));

			return r == null ? string.Empty : r.Text;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="categoryId"></param>
		/// <returns></returns>
		public IEnumerable<Tuple<string, string, string>> GetCodesFromCategory(int categoryId) {
			return LoadDataTable()
				.Where(row => row.CategoryId == categoryId)
				.Select(row =>
					new Tuple<string, string, string>(
						row.Value,
						row.Text,
						row.IsParentNull ? null : row.Parent
						));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="codeFileName"></param>
		/// <returns></returns>
		private static CodeTable LoadDataTableFromFile(string codeFileName) {
			m_logger.DebugFormat("Loading datatable from file {0}.", codeFileName);

			// Prepare schema.
			var schemas = new XmlSchemaSet();
			var xss = typeof(CodeFileProvider)
				.Assembly
				.GetManifestResourceStream("MyLib." + XsdFileName); // Use internal schema.
			using (var xsr = XmlReader.Create(xss)) {
				schemas.Add(null, xsr);
			}

			var dt = new CodeTable();
			if (!System.IO.File.Exists(codeFileName)) {
				return dt;
			}

			// Load code file.
			XElement root;
			using (var codeFileStream = new FileStream(
				codeFileName,
				FileMode.Open,
				FileAccess.Read)) {

				root = XmlExtensions.LoadXElement(codeFileStream, schemas);

				codeFileStream.Close();
			}

			foreach (var catElement in root.Elements(XmlCategoryName)) {
				var catId = Convert.ToInt32(catElement.Attribute(XmlCategoryIdName).Value);
				foreach (var codeElement in catElement.Elements(XmlCodeName)) {
					var row = dt.AddNewRow();

					row.BeginEdit();

					row.CategoryId = catId;
					row.Value = codeElement.Attribute(XmlCodeValueName).Value;
					row.Text= codeElement.Attribute(XmlCodeTextName).Value;
					var pvAttr = codeElement.Attribute(XmlCodeParentName);
					if (pvAttr != null) {
						row.Parent = pvAttr.Value;
					} else {
						row.SetParentNull();
					}

					row.EndEdit();
				}
			}

			dt.AcceptChanges();

			return dt;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="codeFileName"></param>
		/// <param name="dataTable"></param>
		private static void SaveDataTableToFile(string codeFileName, CodeTable dataTable) {
			using (var stream = new FileStream(codeFileName, FileMode.Create, FileAccess.Write)) {
				var writer = new XmlTextWriter(stream, Encoding.UTF8);
				writer.Indentation = 4;
				writer.IndentChar = ' ';
				writer.Formatting = Formatting.Indented;
				writer.WriteStartDocument(false);

				var categoryIdArray = dataTable.Select(row => row.CategoryId) // cat_id;
					.Distinct()
					.ToArray();

				writer.WriteStartElement(XmlCategoriesName);
				writer.WriteAttributeString("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
				writer.WriteAttributeString("xsi", "noNamespaceSchemaLocation", XmlSchema.InstanceNamespace, XsdFileName);

				foreach (var categoryId in categoryIdArray) {
					var codeRows = dataTable.Where(row => row.CategoryId == categoryId);

					WriteCategory(writer, categoryId, codeRows);
				} // end of foreach.

				writer.WriteEndElement();

				writer.WriteEndDocument();

				writer.Flush();
				writer.Close();
			} // end of using.
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="writer"></param>
		/// <param name="categoryId"></param>
		/// <param name="codeRows"></param>
		private static void WriteCategory(XmlWriter writer, int categoryId, IEnumerable<CodeRow> codeRows) {
			writer.WriteStartElement(XmlCategoryName);
			writer.WriteAttributeString(XmlCategoryIdName, categoryId.ToString());

			foreach (var row in codeRows) {
				writer.WriteStartElement(XmlCodeName);

				writer.WriteAttributeString(XmlCodeValueName, row.Value);
				writer.WriteAttributeString(XmlCodeTextName, row.Text);

				if (!row.IsParentNull) {
					var parent = row.Parent; // parentKey.
					if (!string.IsNullOrWhiteSpace(parent)) {
						writer.WriteAttributeString(XmlCodeParentName, parent);
					}
				}

				writer.WriteEndElement();
			}
			writer.WriteEndElement();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="codeFileName"></param>
		public CodeFileProvider(string codeFileName)
			: this(codeFileName, null) {
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="codeFileName"></param>
		/// <param name="cacheProvider"></param>
		public CodeFileProvider(string codeFileName, ICacheProvider cacheProvider)
			: base(cacheProvider) {
			System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(codeFileName));

			codeFileName = codeFileName.Trim();

			m_codeFileName = codeFileName;
			m_cacheKey = GetCacheKeyByFileName(
				System.IO.Path.GetFileName(codeFileName));
		}
	} // end of DataTableCache.
}
